/******************************************************************
 * 个人博客：https://blog.csdn.net/2302_80277720
 * 嵌入式Linux学习交流群：1005210698
 * 欢迎各位大佬和萌新来加入交流学习
 * Change Logs:
 * Date           Author       Notes
 * 2025-08-19     喝呜昂黄    first version
 ******************************************************************/

#include "xf86drm.h"
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

#include "disp_manager.h"
#include "drm_fourcc.h"

static drmModeConnector *conn;        // 连接器的详细信息
static drmModeRes *res;               // DRM设备整体资源
static drmModePlaneRes *plane_res;    // 平面资源

static int fd;               // 文件描述符
static uint32_t conn_id;     // 选中的连接器ID
static uint32_t crtc_id;     // 选中的CRTC ID
static uint32_t plane_id;    // 选中平面ID

static uint32_t color_table[3] = {RED, GREEN, BLUE};

static drm_buf_t buf;                        // 帧缓冲资源的结构体
static property_crtc_t property_crtc;        // CRTC相关属性ID的结构体
static property_planes_t property_planes;    // 平面相关属性ID的结构体
static planes_setting_t planes_setting;      // 平面配置参数结构体

// #define DRM_PAGE_FIIP        // 开启页翻转模式
#ifdef DRM_PAGE_FIIP
static drmEventContext ev;
#endif

static const char *logo = {" \033[33m █████   █████                                         \033[0m\n"
                           " \033[33m░░███   ░░███                                          \033[0m\n"
                           " \033[33m ░███    ░███  █████ ████  ██████   ████████    ███████\033[0m\n"
                           " \033[33m ░███████████ ░░███ ░███  ░░░░░███ ░░███░░███  ███░░███\033[0m\n"
                           " \033[33m ░███░░░░░███  ░███ ░███   ███████  ░███ ░███ ░███ ░███\033[0m\n"
                           " \033[33m ░███    ░███  ░███ ░███  ███░░███  ░███ ░███ ░███ ░███\033[0m\n"
                           " \033[33m █████   █████ ░░████████░░████████ ████ █████░░███████\033[0m\n"
                           " \033[33m░░░░░   ░░░░░   ░░░░░░░░  ░░░░░░░░ ░░░░ ░░░░░  ░░░░░███\033[0m\n"
                           " \033[33m                                               ███ ░███\033[0m\n"
                           " \033[33m                                              ░░██████ \033[0m\n"
                           " \033[33m                                               ░░░░░░  \033[0m\n"};

/**
 * 将子像素排列模式枚举值转换为对应的字符串描述
 *
 * @param sub_pixel: drmModeSubPixel类型的枚举值
 * @return: 指向子像素排列模式描述字符串的指针
 */
static char *get_sub_format(drmModeSubPixel sub_pixel) {
    switch (sub_pixel) {
        case DRM_MODE_SUBPIXEL_HORIZONTAL_RGB:
            return "horizontal RGB";
        case DRM_MODE_SUBPIXEL_HORIZONTAL_BGR:
            return "horizontal BGR";
        case DRM_MODE_SUBPIXEL_VERTICAL_RGB:
            return "vertical RGB";
        case DRM_MODE_SUBPIXEL_VERTICAL_BGR:
            return "vertical BGR";
        case DRM_MODE_SUBPIXEL_NONE:
            return "none";
        case DRM_MODE_SUBPIXEL_UNKNOWN:
        default:
            return "未知的";
    }
}

/**
 * 将连接器类型枚举值转换为对应的字符串描述
 *
 * @param type: 连接器类型枚举值
 * @return: 指向连接器类型描述字符串的指针
 */
static char *get_connector_type(uint32_t type) {
    switch (type) {
        case DRM_MODE_CONNECTOR_Unknown:
            return "未知的";
        case DRM_MODE_CONNECTOR_VGA:
            return "VGA";
        case DRM_MODE_CONNECTOR_DVII:
            return "DVI-I（双链路数字/模拟）";
        case DRM_MODE_CONNECTOR_DVID:
            return "DVI-D（双链路数字）";
        case DRM_MODE_CONNECTOR_DVIA:
            return "DVI-A（模拟）";
        case DRM_MODE_CONNECTOR_Composite:
            return "复合视频（CVBS）";
        case DRM_MODE_CONNECTOR_SVIDEO:
            return "S-视频（Y/C分离）";
        case DRM_MODE_CONNECTOR_LVDS:
            return "LVDS（低压差分信号）";
        case DRM_MODE_CONNECTOR_Component:
            return "分量视频（YPbPr/RGB）";
        case DRM_MODE_CONNECTOR_9PinDIN:
            return "9针DIN（SCART/RS-422）";
        case DRM_MODE_CONNECTOR_DisplayPort:
            return "DisplayPort（DP）";
        case DRM_MODE_CONNECTOR_HDMIA:
            return "HDMI-A（标准HDMI）";
        case DRM_MODE_CONNECTOR_HDMIB:
            return "HDMI-B（双链路HDMI，罕见）";
        case DRM_MODE_CONNECTOR_TV:
            return "电视（射频/AV端子）";
        case DRM_MODE_CONNECTOR_eDP:
            return "eDP";
        case DRM_MODE_CONNECTOR_VIRTUAL:
            return "虚拟连接器（无物理接口）";
        case DRM_MODE_CONNECTOR_DSI:
            return "DSI";
        case DRM_MODE_CONNECTOR_DPI:
            return "DPI";
        case DRM_MODE_CONNECTOR_WRITEBACK:
            return "回写连接器（用于帧缓冲捕获）";
        default:
            return "未定义的连接器类型";
    }
}

/**
 * 将FourCC像素格式编码转换为对应的字符串表示
 *
 * @param fourcc: 32位的FourCC像素格式编码
 * @return: 指向表示该像素格式的字符串指针
 */
static char *fourcc_to_string(uint32_t fourcc) {
    static char buf[5];    // 使用静态缓冲区，避免内存泄漏和野指针问题

    // FourCC是4字节ASCII字符的小端存储，按字节提取并转换
    buf[0] = (fourcc >> 0) & 0xFF;     // 提取最低位字节
    buf[1] = (fourcc >> 8) & 0xFF;     // 提取次低位字节
    buf[2] = (fourcc >> 16) & 0xFF;    // 提取次高位字节
    buf[3] = (fourcc >> 24) & 0xFF;    // 提取最高位字节
    buf[4] = '\0';                     // 字符串终止符

    // 扩展常见格式的映射表
    switch (fourcc) {
        case DRM_FORMAT_ARGB8888:
            return "ARGB8888";
        case DRM_FORMAT_XRGB8888:
            return "XRGB8888";
        case DRM_FORMAT_XBGR8888:
            return "XBGR8888";
        case DRM_FORMAT_ABGR8888:
            return "ABGR8888";
        case DRM_FORMAT_RGB888:
            return "RGB888";
        case DRM_FORMAT_BGR888:
            return "BGR888";
        case DRM_FORMAT_RGB565:
            return "RGB565";
        case DRM_FORMAT_BGR565:
            return "BGR565";
        case DRM_FORMAT_YUYV:
            return "YUYV";
        case DRM_FORMAT_UYVY:
            return "UYVY";
        case DRM_FORMAT_NV12:
            return "NV12";
        case DRM_FORMAT_NV16:
            return "NV16";
        default:
            return buf;    // 未知格式返回原始字符组合
    }
}

/**
 * 创建DRM帧缓冲(FB)及相关缓冲区，并映射到用户空间
 *
 * @param bo: 指向drm_device结构体的指针
 * @return: 0表示创建成功，-1表示过程中出现错误
 */
static int drm_create_fb(drm_buf_t *bo, uint32_t format) {
    int ret;
    // 1. 配置缓冲区参数
    bo->create.width = bo->width;
#ifdef DRM_PAGE_FIIP
    bo->create.height = bo->height * 2;
#else
    bo->create.height = bo->height;
#endif

    if (format == DRM_FORMAT_XRGB8888)
        bo->create.bpp = 32;
    if (format == DRM_FORMAT_RGB888 || format == DRM_FORMAT_BGR888)
        bo->create.bpp = 24;
    if (format == DRM_FORMAT_NV12) {
        bo->create.bpp = 12;
    }

    // 2. 调用IOCTL创建缓冲区：内核返回handle（句柄）、pitch（行跨度）、size（总大小）
    ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &bo->create);
    if (ret < 0) {
        printf("创建缓冲区失败\n");
        return -1;
    }

    printf("== struct drm_mode_create_dumb 详情 ==\n");
    printf("输入参数:\n");
    printf("  width:  %u 像素\n", bo->create.width);
    printf("  height: %u 像素\n", bo->create.height);
    printf("  bpp:    %u 位/像素\n", bo->create.bpp);
    printf("  flags:  %u\n", bo->create.flags);
    printf("输出参数:\n");
    printf("  handle: %u\n", bo->create.handle);
    printf("  pitch:  %u 字节(32对齐)\n", bo->create.pitch);
    printf("  size:   %llu 字节\n", (unsigned long long)bo->create.size);
    printf("======================================\n");

    // 3. 将缓冲区注册为帧缓冲
#if 0
    // 传统模式
    bo->pitch  = bo->create.pitch;     // 保存行跨度
    bo->size   = bo->create.size;      // 保存总大小
    bo->handle = bo->create.handle;    // 保存句柄

    ret = drmModeAddFB(fd, bo->width, bo->height, 24, bo->create.bpp, bo->pitch, bo->handle, &bo->fb_id);
    if (ret < 0) {
        printf("添加FB失败\n");
        return -1;
    }
#else
    uint32_t handles[2] = {0};
    uint32_t pitches[2] = {0};
    uint32_t offsets[2] = {0};

    bo->pitch  = bo->create.pitch;     // 保存行跨度
    bo->size   = bo->create.size;      // 保存总大小
    bo->handle = bo->create.handle;    // 保存句柄

    // 支持多平面像素格式
    if (format == DRM_FORMAT_NV12) {
        // 泰山派摄像头获取的的 NV12 在一个平面内
        // 两个平面共用同一缓冲区句柄
        handles[0] = bo->handle;
        handles[1] = bo->handle;

        // Y平面行跨度为 bo->pitch，UV 平面通常为其1/2（水平采样率1:2）
        pitches[0] = bo->width;
        pitches[1] = bo->width;

        // UV 平面偏移量为 Y 平面大小（width*height）
        offsets[0] = 0;
        offsets[1] = bo->width * bo->height;
    }

    if (format == DRM_FORMAT_XRGB8888 || format == DRM_FORMAT_RGB888 || format == DRM_FORMAT_BGR888) {
        handles[0] = bo->handle;
        pitches[0] = bo->pitch;
    }

    ret = drmModeAddFB2WithModifiers(fd, bo->create.width, bo->create.height, format, handles, pitches, offsets, NULL, &bo->fb_id, 0);
    if (ret < 0) {
        printf("添加FB失败, ret = %d\n", ret);
        return -1;
    }
#endif
    // 4. 映射缓冲区到用户空间
    bo->map.handle = bo->handle;
    ret            = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &bo->map);
    if (ret < 0) {
        printf("映射缓冲区失败\n");
        return -1;
    }

    printf("== struct drm_mode_map_dumb 详情 ==\n");
    printf("输入参数:\n");
    printf("  handle: %u\n", buf.map.handle);
    printf("  填充位: %u\n", buf.map.pad);
    printf("输出参数:\n");
    printf("  offset: 0x%llx\n", (unsigned long long)buf.map.offset);
    printf("===================================\n");

    // 5. 通过mmap将缓冲区映射到用户空间，vaddr可直接读写像素
    bo->vaddr = mmap(0, bo->create.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, bo->map.offset);
    if (bo->vaddr == MAP_FAILED) {
        printf("mmap 失败\n");
        return -1;
    }

    memset(bo->vaddr, 0xff, bo->size);

    return 0;
}

/**
 * 销毁DRM帧缓冲及相关资源
 *
 * @param bo: 指向drm_device结构体的指针
 * @return: 无
 */
static void drm_destroy_fb(drm_buf_t *bo) {
    struct drm_mode_destroy_dumb destroy = {};
    drmModeRmFB(fd, bo->fb_id);     // 移除帧缓冲
    munmap(bo->vaddr, bo->size);    // 解除用户空间映射
    destroy.handle = bo->handle;
    drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy);    // 销毁缓冲区
}

/**
 * 打印DRM对象的属性信息
 *
 * @param fd: DRM设备文件描述符，用于获取属性详情
 * @param props: 指向drmModeObjectProperties结构体的指针
 * @return: 0
 */
static uint32_t property_print(int fd, drmModeObjectProperties *props) {
    drmModePropertyPtr property;
    uint32_t i;

    printf("================ 对象属性（共%d个） ================\n", props->count_props);

    for (i = 0; i < props->count_props; i++) {
        property = drmModeGetProperty(fd, props->props[i]);
        if (!property)
            continue;

        printf("属性名: \"%s\"%*s| ID: %3u | 值: %8ld\n", property->name, (int)(18 - strlen(property->name)), "", props->props[i],
            props->prop_values[i]);
        drmModeFreeProperty(property);
    }
    printf("====================================================\n");

    return 0;
}

/**
 * 获取指定DRM平面的属性ID并存储到平面属性数组中
 *
 * @param fd: DRM设备文件描述符
 * @param plane_id: 目标平面的ID，用于定位具体平面
 * @return: 0表示成功获取并存储属性ID，-1表示获取属性列表失败
 */
static uint32_t get_property_id(int fd, drmModeObjectProperties *props, const char *name) {
    drmModePropertyPtr property;
    uint32_t i, id = 0;

    for (i = 0; i < props->count_props; i++) {
        property = drmModeGetProperty(fd, props->props[i]);
        if (!strcmp(property->name, name))    // 匹配属性名
            id = property->prop_id;           // 获取属性ID

        drmModeFreeProperty(property);
        if (id)
            break;    // 找到后退出循环
    }
    if (!id)
        printf("警告：未找到属性\"%s\"\n", name);

    return id;
}

/**
 * 获取指定DRM平面的关键属性ID并存储
 *
 * @param fd: DRM设备的文件描述符
 * @param plane_id: 目标平面的ID，用于获取其属性信息
 * @return: 0表示成功获取并存储属性ID，-1表示获取属性失败
 */
static uint32_t drm_get_plane_property_id(int fd, uint32_t plane_id) {
    drmModeObjectProperties *props;    // 平面的属性列表
    int num = -1;

    // 查找当前平面在 pp数组中的索引
    for (int i = 0; i < buf.count_plane + 1; i++) {
        if (property_planes.plane_id == plane_id) {
            num = i;
            break;
        } else {
            num = buf.count_plane;    // 新平面，使用当前计数器值
            buf.count_plane++;        // 计数器+1
            break;
        }
    }
    property_planes.plane_id = plane_id;    // 记录平面ID

    // 获取平面的所有属性
    props = drmModeObjectGetProperties(fd, plane_id, DRM_MODE_OBJECT_PLANE);
    if (!props) {
        printf("Failed to get plane properties");
        return -1;
    }

    property_print(fd, props);

    // 保存平面关键属性的ID（用于后续原子操作）
    property_planes.property_fb_id  = get_property_id(fd, props, "FB_ID");     // 绑定帧缓冲
    property_planes.property_crtc_x = get_property_id(fd, props, "CRTC_X");    // 显示位置X
    property_planes.property_crtc_y = get_property_id(fd, props, "CRTC_Y");    // 显示位置Y
    property_planes.property_crtc_w = get_property_id(fd, props, "CRTC_W");    // 显示宽度
    property_planes.property_crtc_h = get_property_id(fd, props, "CRTC_H");    // 显示高度
    property_planes.property_src_x  = get_property_id(fd, props, "SRC_X");     // 源图X
    property_planes.property_src_y  = get_property_id(fd, props, "SRC_Y");     // 源图Y
    property_planes.property_src_w  = get_property_id(fd, props, "SRC_W");     // 源图宽度
    property_planes.property_src_h  = get_property_id(fd, props, "SRC_H");     // 源图高度

    drmModeFreeObjectProperties(props);

    return 0;
}

/**
 * 使用原子模式设置DRM平面的属性配置
 *
 * @param fd: DRM设备的文件描述符
 * @param planes_setting: 指向 planes_setting 结构体的指针
 * @return: 0表示平面配置成功提交，-1表示分配请求失败或提交失败
 */
static uint32_t drm_set_plane(int fd, struct planes_setting *planes_setting) {
    drmModeAtomicReq *req;    // 原子请求结构体
    int i, num;

    // 查找当前平面在pp数组中的索引
    for (i = 0; i < buf.count_plane + 1; i++) {
        if (property_planes.plane_id == planes_setting->plane_id) {
            num = i;
            break;
        }
    }

    // 1. 分配原子请求结构体
    req = drmModeAtomicAlloc();
    if (!req) {
        printf("Failed to alloc atomic req\n");
        return -1;
    }

    // 2. 向原子请求中添加属性修改（所有修改将原子生效）
    // 平面关联CRTC
    drmModeAtomicAddProperty(req, planes_setting->plane_id, property_crtc.property_crtc_id, crtc_id);
    // 平面绑定帧缓冲（FB_ID属性）
    drmModeAtomicAddProperty(req, planes_setting->plane_id, property_planes.property_fb_id, planes_setting->fb_id);
    // 设置平面在CRTC上的位置和大小
    drmModeAtomicAddProperty(req, planes_setting->plane_id, property_planes.property_crtc_x, planes_setting->crtc_x);
    drmModeAtomicAddProperty(req, planes_setting->plane_id, property_planes.property_crtc_y, planes_setting->crtc_y);
    drmModeAtomicAddProperty(req, planes_setting->plane_id, property_planes.property_crtc_w, planes_setting->crtc_w);
    drmModeAtomicAddProperty(req, planes_setting->plane_id, property_planes.property_crtc_h, planes_setting->crtc_h);
    // 设置从帧缓冲中取图的区域（SRC属性为16.16定点数，需左移16位）
    drmModeAtomicAddProperty(req, planes_setting->plane_id, property_planes.property_src_x, planes_setting->src_x << 16);
    drmModeAtomicAddProperty(req, planes_setting->plane_id, property_planes.property_src_y, planes_setting->src_y << 16);
    drmModeAtomicAddProperty(req, planes_setting->plane_id, property_planes.property_src_w, planes_setting->src_w << 16);
    drmModeAtomicAddProperty(req, planes_setting->plane_id, property_planes.property_src_h, planes_setting->src_h << 16);

    // 3. 提交原子请求：所有属性修改原子生效
    if (drmModeAtomicCommit(fd, req, 0, NULL) < 0) {
        printf("Failed to commit atomic req\n");
        drmModeAtomicFree(req);
        return -1;
    }

    // 4. 释放原子请求结构体
    drmModeAtomicFree(req);
    printf("平面配置提交成功：plane_id=%u, fb_id=%u\n", planes_setting->plane_id, planes_setting->fb_id);
    return 0;
}

static uint32_t drm_set_crtc(void) {
    int ret = 0;
    drmModeAtomicReq *req;

    req = drmModeAtomicAlloc();
    if (!req) {
        printf("Failed to alloc atomic req\n");
        return -1;
    }

    // 激活CRTC
    drmModeAtomicAddProperty(req, crtc_id, property_crtc.property_active, 1);
    // 设置CRTC的显示模式
    drmModeAtomicAddProperty(req, crtc_id, property_crtc.property_mode_id, property_crtc.blob_id);
    // 连接器关联CRTC
    drmModeAtomicAddProperty(req, conn_id, property_crtc.property_crtc_id, crtc_id);

#ifdef DRM_PAGE_FIIP
    ret = drmModeAtomicCommit(fd, req, DRM_MODE_ATOMIC_ALLOW_MODESET | DRM_MODE_PAGE_FLIP_EVENT, NULL);
#else
    ret = drmModeAtomicCommit(fd, req, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
#endif
    if (ret < 0) {
        printf("Failed to commit atomic req\n");
        drmModeAtomicFree(req);
        return -1;
    }

    drmModeAtomicFree(req);

    return 0;
}

/**
 * 使用原子模式刷新平面
 *
 * @param fd: DRM设备的文件描述符
 * @param planes_setting: 指向 planes_setting 结构体的指针
 * @return: 0表示平面配置成功提交，-1表示分配请求失败或提交失败
 */
static uint32_t drm_update_plane_fb(int fd, struct planes_setting *planes_setting) {
    drmModeAtomicReq *req;

    req = drmModeAtomicAlloc();
    if (!req) {
        printf("Failed to alloc atomic req\n");
        return -1;
    }

    drmModeAtomicAddProperty(req, planes_setting->plane_id, property_planes.property_fb_id, planes_setting->fb_id);
    drmModeAtomicAddProperty(req, planes_setting->plane_id, property_planes.property_src_y, planes_setting->src_y << 16);

    if (drmModeAtomicCommit(fd, req, 0, NULL) < 0) {
        printf("Failed to commit atomic req\n");
        drmModeAtomicFree(req);
        return -1;
    }

    drmModeAtomicFree(req);

    return 0;
}

/**
 * 从设备所有平面中筛选出同时支持指定像素格式且可关联目标CRTC的平面
 *
 * @param crtc_id: 目标CRTC的索引，用于判断平面是否支持该CRTC
 * @param format: 目标像素格式（fourcc编码），用于筛选支持该格式的平面
 * @return: 0表示成功找到找到符合条件的平面，-1表示未找到或出错
 */
static int find_plane(uint32_t crtc_id, uint32_t format) {
    int ret = 0;
    uint32_t i, j;
    drmModePlanePtr plane;    // 单个平面的详细信息结构体

    // 1. 获取设备上所有平面的资源列表（包含平面ID和数量）
    plane_res = drmModeGetPlaneResources(fd);
    if (!plane_res) {
        printf("drmModeGetPlaneResources failed\n");
        return -1;
    }

    printf("\n========= drmModePlaneRes（平面资源） =========\n");
    printf("平面数量: %d\n", plane_res->count_planes);

    // 2. 遍历所有平面，筛选符合条件的平面
    for (i = 0; i < plane_res->count_planes; ++i) {
        if (i == 1)
            continue;

        uint32_t current_plane_id = plane_res->planes[i];    // 当前遍历的平面ID

        // 2.1 获取当前平面的详细信息（支持的格式、CRTC等）
        plane = drmModeGetPlane(fd, current_plane_id);
        if (!plane) {
            printf("drmModeGetPlane failed for plane %u\n", current_plane_id);
            break;
        }

        printf("-------- 平面 [%u]（ID: %u） --------\n", i, plane->plane_id);
        printf("当前绑定的CRTC ID: %u（0表示未绑定）\n", plane->crtc_id);
        printf("当前绑定的帧缓冲ID: %u（0表示未绑定）\n", plane->fb_id);
        printf("支持的CRTC掩码: 0x%x\n", plane->possible_crtcs);
        printf("目标CRTC索引: %u\n", crtc_id);
        printf("支持的像素格式数量: %u\n", plane->count_formats);
        for (j = 0; j < plane->count_formats; ++j)
            printf("  格式[%u]: %s (%d) %s\n", j, fourcc_to_string(plane->formats[j]), plane->formats[j],
                (plane->formats[j] == format) ? "(匹配目标格式)" : "");
        printf("伽马校正表大小: %u（0表示不支持）\n", plane->gamma_size);

        // 3. 条件1：检查平面是否支持目标CRTC（基于CRTC索引的位掩码判断）
        // possible_crtcs是位掩码，第n位为1表示支持索引为n的CRTC
        if (!(plane->possible_crtcs & (1 << crtc_id))) {
            printf("筛选结果：不支持目标CRTC（索引: %u, ID: %u），跳过\n", crtc_id, crtc_id);
            drmModeFreePlane(plane);
            continue;
        }

        // 4. 条件2：检查平面是否支持目标像素格式
        for (j = 0; j < plane->count_formats; ++j) {
            if (plane->formats[j] == format)
                break;
        }

        // 若遍历完所有格式仍未找到目标格式，则跳过当前平面
        if (j == plane->count_formats) {
            printf("筛选结果：不支持目标像素格式（0x%x），跳过\n\n", format);
            drmModeFreePlane(plane);
            continue;
        }

        // 5. 找到符合条件的平面，记录其ID
        plane_id = plane->plane_id;
        printf("\n筛选结果：找到符合条件的平面！ID: %u\n", plane_id);
        drmModeFreePlane(plane);

        break;
    }

    // 6. 检查是否找到有效平面
    if (i == plane_res->count_planes) {
        // 遍历完所有平面仍未找到，返回错误
        printf("未找到符合条件的平面（格式: 0x%x, CRTC索引: %u）\n", format, crtc_id);
        ret = -1;
    }
    printf("===============================================\n");

    // 7. 释放平面资源结构体
    drmModeFreePlaneResources(plane_res);

    return ret;
}

/**
 * 打印DRM设备的资源信息详情
 *
 * @param 无
 * @return 无
 */
static void drmModeRes_print(void) {
    printf("\n===== DRM设备资源详情 (drmModeRes) =====\n");
    printf("帧缓冲(FB)信息:\n");
    printf("  数量: %d\n", res->count_fbs);
    if (res->count_fbs > 0 && res->fbs) {
        printf("  ID列表: ");
        for (int i = 0; i < res->count_fbs; i++) printf("%u ", res->fbs[i]);
        printf("\n");
    }
    printf("\nCRTC(显示控制器)信息:\n");
    printf("  数量: %d\n", res->count_crtcs);
    if (res->count_crtcs > 0 && res->crtcs) {
        printf("  ID列表: ");
        for (int i = 0; i < res->count_crtcs; i++) printf("%u ", res->crtcs[i]);
        printf("\n");
    }
    printf("\n连接器(Connector)信息:\n");
    printf("  数量: %d\n", res->count_connectors);
    if (res->count_connectors > 0 && res->connectors) {
        printf("  ID列表: ");
        for (int i = 0; i < res->count_connectors; i++) printf("%u ", res->connectors[i]);
        printf("\n");
    }
    printf("\n编码器(Encoder)信息:\n");
    printf("  数量: %d\n", res->count_encoders);
    if (res->count_encoders > 0 && res->encoders) {
        printf("  ID列表: ");
        for (int i = 0; i < res->count_encoders; i++) printf("%u ", res->encoders[i]);
        printf("\n");
    }
    printf("\n支持的显示尺寸范围:\n");
    printf("  宽度: %u (最小) ~ %u (最大) 像素\n", res->min_width, res->max_width);
    printf("  高度: %u (最小) ~ %u (最大) 像素\n", res->min_height, res->max_height);
    printf("======================================\n");
}

/**
 * 打印DRM模式连接器(connector)的详细资源信息
 *
 * @param 无
 * @return 无
 */
static void drmModeConnector_print(void) {
    printf("\n== 连接器资源详情 (drmModeConnector) ==\n");
    printf("连接器信息 (ID: %u)\n", conn->connector_id);
    printf("类型: %s (ID: %u)\n", get_connector_type(conn->connector_type), conn->connector_type_id);
    printf("连接状态: %s\n", conn->connection == DRM_MODE_CONNECTED ? "已连接" : "未连接");
    printf("显示器尺寸: %ux%u mm\n", conn->mmWidth, conn->mmHeight);
    printf("子像素排列: %s\n", get_sub_format(conn->subpixel));
    printf("支持的显示模式数量: %d\n", conn->count_modes);
    if (conn->count_modes > 0) {
        printf("  模式列表（分辨率@刷新率）:\n");
        for (int i = 0; i < conn->count_modes; i++)
            printf("  %d: %ux%u @ %dHz %s\n", i, conn->modes[i].hdisplay, conn->modes[i].vdisplay, conn->modes[i].vrefresh,
                (conn->modes[i].flags & DRM_MODE_TYPE_PREFERRED) ? "(首选)" : "");
    }
    printf("\n可关联的编码器数量: %d\n", conn->count_encoders);
    if (conn->count_encoders > 0) {
        printf("  编码器ID列表: ");
        for (int i = 0; i < conn->count_encoders; i++) printf("%u \n", conn->encoders[i]);
    }
    printf("\n当前关联的编码器ID: %u\n", conn->encoder_id);
    printf("======================================\n\n");
}

/**
 * 初始化DRM设备，完成显示相关资源的配置（原子模式）
 *
 * @param 无
 * @return 0表示初始化成功，-1表示初始化失败
 */
int drm_init(uint32_t format) {
    int ret = 0;
    drmModeAtomicReq *req;
    drmModeObjectProperties *props;

    // 1. 打开DRM设备
    fd = drmOpen("rockchip", NULL);
    if (fd < 0) {
        printf("打开 %s 设备失败\n", "rockchip");
        return -1;
    }
    printf("DRM设备打开成功：fd = %d\n", fd);

    // 2. 获取DRM设备整体资源（CRTC、连接器、编码器列表）
    res = drmModeGetResources(fd);
    if (!res) {
        printf("获取 DRM 资源失败");
        goto err;
    }
    drmModeRes_print();

    // 3. 选择第一个CRTC和连接器
    crtc_id = res->crtcs[0];
    conn_id = res->connectors[0];

    // 4. 开启通用平面支持
    drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);

    // 5. 根据图像格式寻找平面
    ret = find_plane(0, format);
    if (ret < 0)
        goto err1;

    // 7. 获取连接器详细信息（显示模式、连接状态等）
    conn = drmModeGetConnector(fd, conn_id);
    if (!conn) {
        printf("获取连接器失败\n");
        goto err2;
    }
    drmModeConnector_print();

    // 9. 创建帧缓冲
    buf.width  = conn->modes[0].hdisplay;
    buf.height = conn->modes[0].vdisplay;
    ret        = drm_create_fb(&buf, format);
    if (ret < 0)
        goto err3;

    // 10. 开启原子模式支持
    drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1);

    // 11. 获取连接器属性：主要获取"CRTC_ID"属性的ID（用于关联CRTC）
    props = drmModeObjectGetProperties(fd, conn_id, DRM_MODE_OBJECT_CONNECTOR);
    if (!props) {
        printf("获取连接器属性失败\n");
        goto err4;
    }
    printf("连接器属性:\n");
    property_print(fd, props);
    property_crtc.property_crtc_id = get_property_id(fd, props, "CRTC_ID");    // 连接器关联CRTC的属性ID
    drmModeFreeObjectProperties(props);

    // 12. 获取CRTC属性："ACTIVE"（激活）和"MODE_ID"（显示模式）
    props = drmModeObjectGetProperties(fd, crtc_id, DRM_MODE_OBJECT_CRTC);
    if (!props) {
        printf("获取CRTC属性失败\n");
        goto err5;
    }
    printf("CRTC 属性:\n");
    property_print(fd, props);
    property_crtc.property_active  = get_property_id(fd, props, "ACTIVE");     // 激活CRTC的属性ID
    property_crtc.property_mode_id = get_property_id(fd, props, "MODE_ID");    // 设置模式的属性ID
    drmModeFreeObjectProperties(props);

    // 13. 创建显示模式的blob：将显示模式封装为blob，供CRTC设置使用
    ret = drmModeCreatePropertyBlob(fd, &conn->modes[0], sizeof(conn->modes[0]), &property_crtc.blob_id);
    if (ret < 0) {
        printf("创建模式blob失败\n");
        goto err5;
    }
    printf("显示模式blob创建成功：blob_id=%u\n", property_crtc.blob_id);

    // 14. 原子模式初始化CRTC和连接器：激活CRTC、设置显示模式、连接器关联CRTC
    req = drmModeAtomicAlloc();    // 分配原子请求
    if (!req) {
        printf("分配原子请求失败\n");
        goto err6;
    }

    ret = drm_set_crtc();
    if (ret < 0)
        goto err6;

    printf("CRTC和连接器初始化成功\n");

    return 0;

err6:
    drmModeDestroyPropertyBlob(fd, property_crtc.blob_id);
err5:
    drmModeFreeObjectProperties(props);
err4:
    drm_destroy_fb(&buf);
err3:
    drmModeFreeConnector(conn);
err2:
    drmModeFreePlaneResources(plane_res);
err1:
    drmModeFreeResources(res);
err:
    close(fd);

    return -1;
}

/**
 * 释放DRM相关的所有资源并退出
 *
 * @return: 无
 */
void drm_exit(void) {
    drm_destroy_fb(&buf);          // 销毁帧缓冲
    drmModeFreeConnector(conn);    // 释放连接器信息
    drmModeFreeResources(res);     // 释放设备资源
    close(fd);                     // 关闭设备文件描述符
    printf("DRM资源已释放\n");
}

#ifdef DRM_PAGE_FIIP
static void drm_page_flip_handler(int fd, uint32_t frame, uint32_t sec, uint32_t usec, void *data) {
    static int count = 0;

    // 切换显示
    if (count == 0)
        count = 1;
    else if (count == 1)
        count = 0;

    // 设置偏移量
    if (count == 1)
        planes_setting.src_y = buf.height;
    else
        planes_setting.src_y = 0;

    drm_update_plane_fb(fd, &planes_setting);

    drm_set_crtc();
}
#endif

// ----------------------------- NV12 test start  -----------------------------------
void rgb888_to_yuv420(uint8_t r, uint8_t g, uint8_t b, uint8_t *y, uint8_t *u, uint8_t *v) {
    *y = (uint8_t)(0.299 * r + 0.587 * g + 0.114 * b);
    *u = (uint8_t)(128 - 0.168736 * r - 0.331264 * g + 0.5 * b);
    *v = (uint8_t)(128 + 0.5 * r - 0.418688 * g - 0.081312 * b);
}
void generate_solid_color_nv12(int width, int height, uint8_t target_rgb[3], uint32_t *nv12_buf) {
    // -------------------- 1. 计算关键尺寸 --------------------
    int y_width  = width;                 // Y 平面宽度（720）
    int y_height = height;                // Y 平面高度（1280）
    int y_size   = y_width * y_height;    // Y 平面总字节数（720 * 1280=921600）

    int uv_width  = y_width / 2;                 // UV 平面宽度（360）
    int uv_height = y_height / 2;                // UV 平面高度（640）
    int uv_size   = uv_width * uv_height * 2;    // UV 平面总字节数（360 * 640 * 2=460800）

    // -------------------- 2. 计算目标颜色的 YUV 值 --------------------
    uint8_t y_val, u_val, v_val;
    rgb888_to_yuv420(target_rgb[0], target_rgb[1], target_rgb[2], &y_val, &u_val, &v_val);

    // -------------------- 3. 填充 Y 平面（转换为 uint8_t* 操作） --------------------
    uint8_t *y_plane = (uint8_t *)nv12_buf;    // 将 uint32_t* 转换为 uint8_t* 访问字节流
    memset(y_plane, y_val, y_size);            // 直接填充 Y 平面（按字节操作）

    // -------------------- 4. 填充 UV 平面（转换为 uint8_t* 操作） --------------------
    uint8_t *uv_plane = y_plane + y_size;    // UV 平面起始地址（Y 平面之后）
    for (int i = 0; i < uv_size; i += 2) {
        uv_plane[i]     = u_val;    // 填充 U 分量（按字节操作）
        uv_plane[i + 1] = v_val;    // 填充 V 分量（按字节操作）
    }
}
// ----------------------------- NV12 test end  -------------------------------------

// 函数功能：通过fb_id获取当前屏幕使用的像素格式（基于你的DRM IOCTL环境）
// 参数说明：
//   drm_fd：已打开的DRM设备文件描述符（如/dev/dri/card0）
//   fb_id：目标帧缓冲ID（前文日志中为125）
//   out_fourcc：传出参数，存储当前像素格式的FourCC编码（可为NULL）
// 返回值：0成功，-1失败
int get_current_pixel_format() {
    // 1. 初始化结构体（必须清零，避免残留数据影响IOCTL）
    struct drm_mode_fb_cmd2 fb_cmd;
    memset(&fb_cmd, 0, sizeof(fb_cmd));
    fb_cmd.fb_id = 125;    // 关键：传入要查询的帧缓冲ID（125）

    // 2. 调用DRM_IOCTL_MODE_GETFB2获取帧缓冲属性（核心IOCTL）
    if (drmIoctl(fd, DRM_IOCTL_MODE_GETFB, &fb_cmd) < 0) {
        fprintf(stderr, "[ERROR] 获取FB(%u)属性失败\n", fb_cmd.fb_id);
        return -1;
    }

    // 3. 打印当前屏幕的关键信息（验证结果）
    printf("\n================ 当前屏幕显示信息 ================\n");
    printf("帧缓冲ID（fb_id）: %u\n", fb_cmd.fb_id);
    printf("显示分辨率      : %u x %u 像素\n", fb_cmd.width, fb_cmd.height);
    printf("当前像素格式    : FourCC=0x%08X -> %s\n", fb_cmd.pixel_format, fourcc_to_string(fb_cmd.pixel_format));
    printf("行跨度（pitch）  : 第0平面 = %u 字节\n", fb_cmd.pitches[0]);
    printf("==================================================\n");

    return 0;
}

int main(int argc, char **argv) {
    int ret;
    uint32_t format = DRM_FORMAT_XRGB8888;
    // uint32_t format = DRM_FORMAT_NV12;
    // uint32_t format = DRM_FORMAT_RGB888;

    printf("\n%s\n", logo);

#ifdef DRM_PAGE_FIIP
    ev.version           = DRM_EVENT_CONTEXT_VERSION;
    ev.page_flip_handler = drm_page_flip_handler;
#endif

    // 1. 初始化DRM
    ret = drm_init(format);
    if (ret < 0) {
        printf("DRM初始化失败\n");
        return -1;
    }

    // 2. 获取平面的属性ID
    ret = drm_get_plane_property_id(fd, plane_id);
    if (ret < 0)
        goto exit;

    // 3. 填充帧缓冲：将缓冲区分为3部分，分别填充红、绿、蓝
    if (format == DRM_FORMAT_NV12) {
        uint8_t red_rgb[3] = {255, 0, 0};    // 目标颜色：红色

        // 生成纯色 NV12 图像（传入 uint32_t* 缓冲区）
        generate_solid_color_nv12(buf.width, buf.height, red_rgb, buf.vaddr);
    }
    if (format == DRM_FORMAT_XRGB8888) {
#ifdef DRM_PAGE_FIIP
        for (int i = 0; i < buf.width * buf.height; i++) buf.vaddr[i] = RED;
        for (int i = buf.width * buf.height; i < buf.width * buf.height * 2; i++) buf.vaddr[i] = BLUE;
#else
        for (int j = 0; j < 3; j++)
            for (int i = j * buf.width * buf.height / 3; i < (j + 1) * buf.width * buf.height / 3; i++) buf.vaddr[i] = color_table[j];
#endif
    }
    if (format == DRM_FORMAT_RGB888 || format == DRM_FORMAT_BGR888) {
        int width           = buf.width;     // 屏幕宽度
        int height          = buf.height;    // 屏幕高度
        int pitch           = buf.pitch;     // 行跨度
        int bytes_per_pixel = 3;             // RGB888每个像素3字节

        // 三等分屏幕高度（按行划分，而非按总像素）
        int lines_per_segment = height / 3;    // 每个颜色区域的行数

        uint8_t *buf_rgb = (uint8_t *)buf.vaddr;

        for (int j = 0; j < 3; j++) {
            // 提取当前颜色的RGB分量
            uint8_t r = (color_table[j] >> 16) & 0xFF;
            uint8_t g = (color_table[j] >> 8) & 0xFF;
            uint8_t b = color_table[j] & 0xFF;

            // 计算当前颜色区域的行范围
            int start_line = j * lines_per_segment;
            int end_line   = (j == 2) ? height : (j + 1) * lines_per_segment;    // 最后一段覆盖剩余行

            // 逐行填充当前区域
            for (int line = start_line; line < end_line; line++) {
                // 计算当前行的起始地址（考虑pitch的对齐填充）
                uint8_t *line_start = buf_rgb + line * pitch;

                // 填充当前行的所有有效像素（跳过对齐字节）
                for (int x = 0; x < width; x++) {
                    int pixel_offset             = x * bytes_per_pixel;
                    line_start[pixel_offset]     = r;    // R分量
                    line_start[pixel_offset + 1] = g;    // G分量
                    line_start[pixel_offset + 2] = b;    // B分量
                }
            }
        }
    }
    printf("帧缓冲填充完成：红、绿、蓝三部分\n");

    // 4. 配置平面
    planes_setting.plane_id = plane_id;
    planes_setting.fb_id    = buf.fb_id;
    // 平面在CRTC上的显示位置和大小（全屏显示）
    planes_setting.crtc_x = 0;
    planes_setting.crtc_y = 0;
    planes_setting.crtc_w = buf.width;
    planes_setting.crtc_h = buf.height;
    // 从帧缓冲中取图的区域（全图）
    planes_setting.src_x = 0;
    planes_setting.src_y = 0;
    planes_setting.src_w = buf.width;
    planes_setting.src_h = buf.height;

    // 提交平面配置
    ret = drm_set_plane(fd, &planes_setting);
    if (ret < 0)
        goto exit;

#ifdef DRM_PAGE_FIIP
    sleep(3);
    printf("switch\n");
    drmHandleEvent(fd, &ev);

    sleep(3);
    printf("switch\n");
    drmHandleEvent(fd, &ev);
#else
    sleep(3);
#endif
    printf("exit\n");

exit:
    // 5. 释放所有资源
    drm_exit();
    // fclose(fp1);

    return 0;
}